#include "media_lib.h"

#define BUFFSIZE 256

//内部接口：解析某一个频道目录所要获取的所有内容
struct chn_context_st {
	struct mlib_st mlib_list; //频道列表
#if 0
	char **mp3_path; 
	int mp3_nr;
#endif
	glob_t mp3_path; //音频路径保存在mp3_path.gl_pathv指针数组中
					 //音频文件的个数保存在mp3_path.gl_pathc中
	int mp3_index; //当前第mp3_index+1音频文件对应的索引
	int mp3_fd; //当前第mp3_index+1音频文件对应的文件描述符
	int mp3_pos; //读到了第mp3_index+1音频文件的位置
};

/*
typedef struct {
    size_t gl_pathc;    匹配模式的路径计数
    char **gl_pathv;    匹配的路径名列表
    size_t gl_offs;     要在gl_pathv中保留的槽位
} glob_t;
在使用 glob() 函数时，你需要初始化一个 glob_t 结构体，然后将其传递给 glob() 函数，该函数会填充结构体中的信息，包括匹配到的路径数量和路径列表。使用完后，通常需要调用 globfree() 函数释放内存。
*/
//打开音频文件的下一个, 进行读取
static int __open_next (struct chn_context_st *mychn);
struct chn_context_st *__get_chn_context (const char *path);

//每一个频道都有一个chn_context_st结构, 最多有MEDIA_MAX_CHN_NR频道,频道号与对应的数组下标相等
struct chn_context_st *chns[MEDIA_MAX_CHN_NR + 1];

/*
   获取频道列表
*/
int mlib_get_chn_list (struct mlib_st **mlib, int *chn_nr)
{
	DIR *dp;
	struct dirent *entry;
	int len;
	char buf[BUFFSIZE] = {};
	int i = 0;
	struct chn_context_st *context;

	//解析媒体库得到每一个频道的路径 
	//媒体库的路径：/home/andy/media_lib
	//频道的路径：/home/andy/media_lib/music 
	dp = opendir (MEDIA_PATH);
	/*
	DIR *opendir(const char *name);
	参数：
	const char *name：要打开的目录的路径名。
	返回值：
	成功：返回一个指向 DIR 类型的指针，该指针代表了打开的目录流。这个指针后续可以被用来在 readdir() 函数中读取目录内容。
	失败：返回 NULL 并且设置 errno 以指示发生的错误。
	*/
	if (NULL == dp) {
		perror ("opendir()");
		return -1;
	}
	while (1) {
		entry = readdir (dp);
		/*
		struct dirent *readdir(DIR *dirp);
		参数：
		DIR *dirp：通过 opendir() 返回的指向目录流的指针。
		返回值：
		成功：返回一个指向 struct dirent 结构体的指针，该结构体包含了下一个目录项的信息。
		失败或到达目录末尾：返回 NULL。此时可通过检查 errno 来确定是否发生了错误。
		struct dirent {
    	ino_t          d_ino;       inode编号
    	off_t          d_off;       向下一个方向偏移
    	unsigned short d_reclen;    记录的长度
   	 	unsigned char  d_type;      文件类型
    	char           d_name[256]; 文件名
	};
		*/
		if (NULL == entry) {
			break;
		}
		//entry->d_name 频道名
		memset (buf, '\0', BUFFSIZE);
		len = BUFFSIZE;
		strncpy (buf, MEDIA_PATH, len);//len指的是最大复制的字符数，不足会用空字符填充
		len -= (strlen(buf) + 1);
		strncat (buf, "/", len);//len 指最大追加的字符数，该函数会将源字符串中的最多n个字符追加到目标字符串的末尾，并在最后添加空字符。
		len -= 1;
		//buf 中保存的就是每一个频道的路径
		strncat (buf, entry->d_name, len);
		
		//获取频道的内容
		context = __get_chn_context (buf);
		if (NULL == context) {
			continue;
		}
		*mlib = realloc (*mlib, (i+1) * sizeof (struct mlib_st));
		if (NULL == *mlib) {
			return -1;
		}
		(*mlib)[i] = context->mlib_list;
		chns [context->mlib_list.chnid] = context;
		i++;
	}
	//值-结果参数,chn_nr保存的是频道的数量
	*chn_nr = i;
	return 0;
}


/*
   给定一个频道的路径
   媒体库：/home/andy/media_lib
   频道：/home/andy/media_lib/music(opera)(traffic)
 */
struct chn_context_st *__get_chn_context (const char *path)
{
	FILE* fp;
	char buf[BUFFSIZE] = {};
	struct chn_context_st *mychn;
	size_t len;
	static int cur_chn_id = 1;
	int ret = 0;

	strncpy (buf, path, BUFFSIZE);
	//解析频道的描述文件
	//buf中保存的是频道描述文件的路径
	strncat (buf, "/descr.txt", BUFFSIZE - strlen(path));

	//打开频道的描述文件
	fp = fopen (buf, "r");
	if (NULL == fp) {
		return NULL;
	}
	//开辟一段空间, 将描述文件的内容读出来
	mychn = malloc (sizeof (struct chn_context_st));
	if (NULL == mychn) {
		fclose (fp);
		return NULL;
	}
	len = 0;
	mychn->mlib_list.descr = NULL;
	/*
	ssize_t getline(char **lineptr, size_t *n, FILE *stream);
	其中，lineptr是一个指向指针的指针，用于存储读取的文本行；n是一个指向lineptr所指向的缓冲区大小的指针，			stream是一个指向被读取的文件流的指针。该函数会自动分配足够的内存来存储读取的文本行，并通过lineptr和n返回缓	冲区的地址和大小。

	成功时，该函数返回读取的字符数（包括换行符），如果到达文件结尾或发生错误，则返回-1。
	*/
	if (-1 == getline (&mychn->mlib_list.descr, &len, fp)) {
		free (mychn);
		mychn = NULL;
		fclose (fp);
		return NULL;
	}
	//存储当前频道下的所有音频文件的路径 
	strncpy (buf, path, BUFFSIZE);
	strncat (buf, "/*.mp3", BUFFSIZE - strlen(path));
	//buf--> /home/andy/media_lib/music/*.mp3 
	memset (&mychn->mp3_path, '\0', sizeof (glob_t));
	ret = glob (buf, 0, NULL, &mychn->mp3_path);
	/*
		int glob(const char *pattern, int flags, int (*errfunc)(const char *epath, int 	eerrno), glob_t *pglob);
	pattern: 要匹配的文件名或路径的模式。可以使用通配符，例如 * 表示零个或多个字符，? 表示一个字符。
	flags: 控制 glob() 行为的标志，可以是以下之一或其组合：
	GLOB_MARK: 在目录名末尾添加一个 /。
	GLOB_NOSORT: 不对结果排序。
	GLOB_NOCHECK: 如果没有匹配的文件，则返回模式本身。
	GLOB_APPEND: 将结果附加到现有 glob_t 结构中。
	GLOB_NOESCAPE: 不考虑反斜杠转义。
	等等。
	errfunc: 如果在搜索过程中发生错误，glob() 会调用这个函数。可以设置为 NULL 以忽略错误。
	pglob: 一个指向 glob_t 结构的指针，用于存储匹配到的路径名。
	glob() 返回 0 表示成功，非零值表示错误。常见的错误包括：
	
	GLOB_NOMATCH: 没有找到匹配的路径。
	GLOB_ABORTED: 遇到错误并被中止。
	GLOB_NOSPACE: 内存不足。
	在使用 glob() 之后，应该调用 globfree() 来释放 glob_t 结构中分配的内存。
	*/
	if (GLOB_NOMATCH == ret) {
		//该频道没有音频文件 
		printf("描述是：%s该频道没有音频文件!\n", mychn->mlib_list.descr);
		fclose(fp);
		free (mychn);
		return NULL;
	}

	//该频道是一个有效频道 
	mychn->mlib_list.chnid = cur_chn_id++;

	//当前频道对应的存储所有频道路径的数组下标
	mychn->mp3_index = 0;
	mychn->mp3_pos = 0;
	mychn->mp3_fd = open ((mychn->mp3_path).gl_pathv[mychn->mp3_index], O_RDONLY);
	return mychn;
}

ssize_t mlib_read_chn_data (int8_t chnid, void *buf, size_t size)
{
	ssize_t cnt = 0;
	//chns[chnid] 就是chnid频道对应的频道结构 
	struct chn_context_st *mychn= chns[chnid];

	//所有的音频文件的路径 mychn->mp3_path.gl_pathv 
	while (1) {
		cnt = pread (mychn->mp3_fd, buf, size, mychn->mp3_pos);
		/*
	pread是一个Unix/Linux系统调用，用于从文件的指定位置读取数据，不会更新文件偏移量。它的原型是：
	ssize_t pread(int fd, void *buf, size_t count, off_t offset);
	其中，fd是文件描述符，buf是存储读取数据的缓冲区的指针，count是要读取的字节数，offset是从文件中的指定偏移量开始读取数据。与read系统调用不同的是，pread不会修改文件偏移量，而是从指定的偏移量处开始读取数据。

	pread的优点之一是，它可以在多线程环境下安全地读取文件，因为它不会更改文件偏移量，避免了多个线程之间的竞争条件。
		*/
		if (-1 == cnt) {
			perror ("pread()");
			return -1;
		} else if (0 == cnt) {
			//当前文件读完了, 关闭当前文件读取下一个
			__open_next (mychn);
		} else {
			break;
		}
	}
	//改变文件的偏移位置
	mychn->mp3_pos += cnt;
	return cnt;
}

//关闭当前的文件描述符, 打开下一个并读取
static int __open_next (struct chn_context_st *mychn)
{
	//关闭当前的
	close (mychn->mp3_fd);
	mychn->mp3_index++;
	if (mychn->mp3_index == mychn->mp3_path.gl_pathc) 
	{
		mychn->mp3_index = 0;
	}
	//打开下一个
	mychn->mp3_fd = open ((mychn->mp3_path.gl_pathv)[mychn->mp3_index], O_RDONLY);
	if (-1 == mychn->mp3_fd) {
		perror ("open()");
		return -1;
	}
	mychn->mp3_pos = 0;
	return 0;
}
